업무 모듈 디렉토리 깊게 파헤치기

Areas, Controllers, Pages, Contracts

QCN

ASP.NET Core 공유 프레임워크 사용

  • .NET Core 3.0 이후 클래스 라이브러리에서 ASP.NET Core API 사용하도록 개선되었습니다.
  • Microsoft.NET.Sdk 또는 Microsoft.NET.Sdk.Razor SDK를 사용하는 프로젝트는 공유 프레임워크에서 ASP.NET Core API를 사용하려면 ASP.NET Core를 참조합니다.
  • MVC 확장성 포함 Razor 보기 또는 Razor 페이지 지원이 가능합니다.
<Project Sdk="Microsoft.NET.Sdk.Razor">
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
</Project>
QCN

Area란 무엇인가?

  • ASP.NET Core 에서 컨트롤러, 뷰, 모델을 논리적으로 구분해 영역별로 모듈화하기 위한 기능입니다.
  • Area 기능으로 모듈 간의 엔드포인트의 충돌을 근본적으로 방지합니다.
/{area}/{controller}/{action}/{id?}
/Areas
   /Admin
      /Controllers
         DashboardController.cs
      /Views
         /Dashboard
            Index.cshtml
      /Models
QCN

API의 심장: Controllers 디렉토리

  • 위치: Areas/[모듈 ID]/Controllers
  • 역할: 클라이언트(웹 브라우저 등)로부터 오는 API 요청을 받아 처리하는 로직이 담겨있습니다.
  • 파일: HomeController.cs, BoardController.cs 와 같은 C# 클래스 파일로 구성됩니다.
  • 주로 JSON 형태의 데이터를 주고받는 RESTful API 엔드포인트를 정의합니다.
/Areas
└── sample
    └── Controllers
        └── HomeController.cs
QCN

Controllers 예시 코드

  • /api/[모듈 ID]/home/hello 요청을 처리하는 간단한 예시입니다.
  • 다음 코드는 GET /api/sample/home/hello 요청 시 "Hello from Server!" 라는 텍스트를 반환합니다.
using Microsoft.AspNetCore.Mvc;

namespace sample.Areas.sample.Controllers
{
    [Area("sample")]
    [Route("[area]/api/[controller]")]
    public class HomeController : Controller
    {
        [HttpGet("hello")]
        public ActionResult<string> SayHello()
        {
            return "Hello from Server!";
        }
    }
}
QCN

서버가 그리는 화면: Pages 디렉토리

  • 위치: Areas/[모듈 ID]/Pages
  • 역할: 서버 측에서 렌더링되는 UI, 즉 사용자에게 보여질 화면(HTML)을 만듭니다.
  • 파일: 주로 Razor Pages 기술을 사용하며, Index.cshtml(View)과 Index.cshtml.cs(Code-behind) 파일이 하나의 쌍으로 동작합니다.
  • 서버의 데이터를 받아 동적인 웹 페이지를 생성할 때 사용됩니다.
/Areas
└── Sample
    └── Pages
        ├── Index.cshtml
        └── Index.cshtml.cs
QCN

Pages 파일의 역할

  • Index.cshtml

    • HTML 구조와 함께 C# 코드를 사용하여 서버 데이터를 화면에 표시합니다.
    • @ 기호를 사용하여 C# 변수나 로직을 HTML에 삽입합니다.
  • Index.cshtml.cs (코드 비하인드)

    • 페이지가 로드될 때 실행될 C# 로직을 담습니다.
    • 데이터베이스에서 데이터를 조회하거나, 클라이언트 요청을 처리하여 그 결과를 cshtml 파일로 전달합니다.
  • Razor 구문을 사용하는 ASP.NET 웹 프로그래밍 소개

QCN

계약의 정의: Contracts 디렉토리

  • 위치: [모듈 ID]/Contracts 화면/기능 개발자의 메인 디렉토리입니다.
  • 컴파일 할 때 모든 모듈의 환경 설정과 개발 소스는 프론트엔드와 백엔드 모두 $(HANDSTACK_HOME)/contracts 단일 디렉토리내에서 관리됩니다.
  • 텍스트 편집기 기반의 개발 도구로 컴파일 없이 앱 개발 가능합니다.
[모듈 ID]/contracts
├─dbclient
├─function
├─repository
├─transact
└─wwwroot
QCN

contracts 디렉토리 구조

프로젝트 관리에 필요한 소스 운영 기준을 만들기 위해 디렉토리와 파일 명명에 대한 규칙을 다음과 같이 권장합니다.

module
   └─HDS: 기본 애플리케이션 ID 3자리
       └─TST: 프로젝트 ID 3자리
           └─TST010: 파일 ID 
QCN

어플리케이션 계약 디렉토리

HandStack에서 공식 제공되는 모든 디렉토리와 소스 코드 들은 이 규칙을 준수합니다.

%HANDSTACK_HOME%\contracts
├─dbclient
│  └─HDS
│      └─TST
├─function
│  ├─csharp
│  │  └─HDS
│  │      └─TST
│  │          └─TST010
│  └─javascript
│      └─HDS
│          └─TST
│              └─TST020
├─repository
│  └─HDS
├─transact
│  └─HDS
│      ├─STR
│      └─TST
└─wwwroot
   └─HDS
       └─TST
QCN

그외에 모듈 내 디렉토리 구조

  • 모듈의 가독성과 유지보수성을 위해 다음과 같은 디렉토리 구조를 권장합니다.

  • Entity: 데이터베이스 테이블과 매핑되는 모델 클래스

  • Events: 비동기 처리를 위한 이벤트 정의

  • Extensions: 기존 클래스를 확장하는 헬퍼 메서드

  • Services: 비즈니스 로직을 처리하는 서비스 클래스

  • Settings: 모듈별 환경 설정 값

QCN

HandStack의 ack 서버

  • ack 서버는 HandStack 애플리케이션의 시작점입니다.
  • 이 웹 서버가 시작될 때, 필요한 모듈들을 로딩하여 메모리에 올립니다.
  • 그 후, 클라이언트로부터 들어오는 모든 HTTP 요청(API, Page)을 각 모듈의 적절한 ControllerPage로 전달하고 응답을 처리하는 중심 역할을 수행합니다.
  • ack 서버의 appsettings.json 에 로드되는 모듈 정보를 확인하세요.
QCN

로딩되는 모듈 확인하기

  • ack 서버가 어떤 모듈을 로드하는지는 설정 파일에서 확인할 수 있습니다.
  • 아래 경로의 파일을 열어 LoadModules 항목을 살펴봅시다.
    • 경로: $(HANDSTACK_SRC)/1.WebHost/ack/appsettings.json
{
  // ... 생략 ...
  "LoadModules": [
      "wwwroot",
      "transact",
      "dbclient",
      "function",
      "repository",
      "logger",
      "checkup"
  ],
  // ... 생략 ...
}
QCN

checkup 모듈 설정 확인하기

{
    "ModuleID": "checkup",
    "Name": "checkup",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "ManagedAccessKey": "6eac215f2f5e495cad4f2abfdcad7644",
        "EncryptionAES256Key": "1234567890123456",
        "AdministratorEmailID": "administrator@handstack.kr",
        "ModuleConfigurationUrl": "http://localhost:8421/checkup/api/managed/initialize-settings",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "ModuleLogFilePath": "../log/checkup/module.log",
        "ModuleBasePath": "../modules/checkup",
        "WWWRootBasePath": "../modules/checkup/wwwroot/checkup",
        "EventAction": [
            "repository.Events.RepositoryRequest"
        ],
        "SubscribeAction": [],
        "ConnectionString": "URI=file:../sqlite/HDS/dbclient/checkup.db;Journal Mode=Off;BinaryGUID=False;DateTimeFormat=Ticks;Version=3;"
    }
}
QCN

dbclient 모듈 설정 확인하기

dbclient 모듈 참고하기

{
    "ModuleID": "dbclient",
    "Name": "dbclient",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "CircuitBreakResetSecond": 60,
        "DefaultCommandTimeout": 30,
        "ContractBasePath": [
            "../contracts/dbclient"
        ],
        "IsTransactionLogging": false,
        "ModuleLogFilePath": "../log/dbclient/module.log",
        "IsLogServer": true,
        "LogServerUrl": "http://localhost:8421/logger/api/log/insert",
        "IsProfileLogging": false,
        "ProfileLogFilePath": "../log/dbclient/profile.log",
        "EventAction": [],
        "SubscribeAction": [
            "dbclient.Events.DbClientRequest",
            "dbclient.Events.ManagedRequest"
        ],
        "DataSource": [
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "CHECKUPDB",
                "DataProvider": "SQLite",
                "ConnectionString": "URI=file:../sqlite/HDS/dbclient/checkup.db;Journal Mode=Off;BinaryGUID=False;DateTimeFormat=Ticks;Version=3;",
                "IsEncryption": "N",
                "Comment": "SQLite 기본 데이터베이스"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "DB01",
                "DataProvider": "SQLite",
                "ConnectionString": "URI=file:../sqlite/HDS/dbclient/HDS.db;Journal Mode=Off;BinaryGUID=False;DateTimeFormat=Ticks;Version=3;",
                "IsEncryption": "N",
                "Comment": "SQLite 기본 데이터베이스"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "DB02",
                "DataProvider": "SqlServer",
                "ConnectionString": "Data Source=localhost;Initial Catalog=master;User ID=sa;Password=Strong@Passw0rd;",
                "IsEncryption": "N",
                "Comment": "SqlServer 기본 데이터베이스"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "DB03",
                "DataProvider": "Oracle",
                "ConnectionString": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SID=ORCL)));User Id=system;Password=Strong@Passw0rd;",
                "IsEncryption": "N",
                "Comment": "Oracle 기본 데이터베이스"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "DB04",
                "DataProvider": "MySQL",
                "ConnectionString": "Server=localhost;Port=3306;Uid=root;Pwd=Strong@Passw0rd;PersistSecurityInfo=True;SslMode=none;Charset=utf8;Allow User Variables=True;",
                "IsEncryption": "N",
                "Comment": "MySQL 기본 데이터베이스"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "DB05",
                "DataProvider": "PostgreSQL",
                "ConnectionString": "Host=localhost;Port=5432;Database=postgres;User ID=postgres;Password=Strong@Passw0rd;",
                "IsEncryption": "N",
                "Comment": "PostgreSQL 기본 데이터베이스"
            }
        ]
    }
}
QCN

function 모듈 설정 확인하기

nuget, npm, pypi 패키지 관리자로 서버 함수에 필요한 패키지를 관리합니다.

function 모듈 참고하기

{
    "ModuleID": "function",
    "Name": "function",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "CircuitBreakResetSecond": 60,
        "IsLogServer": true,
        "LogServerUrl": "http://localhost:8421/logger/api/log/insert",
        "ContractBasePath": [
            "../contracts/function"
        ],
        "ModuleLogFilePath": "../log/function/module.log",
        "NodeFunctionConfig": {
            "LocalStoragePath": "../cache/function",
            "LogMinimumLevel": "trace",
            "FileLogBasePath": "../log/function/javascript",
            "TimeoutMS": -1,
            "IsSingleThread": true,
            "WatchGracefulShutdown": true,
            "EnableFileWatching": true,
            "WatchFileNamePatterns": [ "featureMain.js" ],
            "NodeAndV8Options": "",
            "EnvironmentVariables": ""
        },
        "CSharpFunctionConfig": {
            "EnableFileWatching": true,
            "FileLogBasePath": "../log/function/csharp",
            "WatchFileNamePatterns": [ "featureMain.cs" ],
        },
        "EventAction": [],
        "SubscribeAction": [],
        "FunctionSource": [
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "FN01",
                "DataProvider": "SQLite",
                "ConnectionString": "URI=file:../sqlite/HDS/dbclient/HDS.db;Journal Mode=Off;BinaryGUID=False;DateTimeFormat=Ticks;Version=3;",
                "IsEncryption": "N",
                "WorkingDirectoryPath": "../tmp/HDS/function/HDS_FN01",
                "Comment": "SQLite 기본 거래"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "FN02",
                "DataProvider": "SqlServer",
                "ConnectionString": "Data Source=localhost;Initial Catalog=master;User ID=sa;Password=Strong@Passw0rd;",
                "IsEncryption": "N",
                "WorkingDirectoryPath": "../tmp/HDS/function/HDS_FN02",
                "Comment": "SqlServer 기본 거래"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "FN03",
                "DataProvider": "Oracle",
                "ConnectionString": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SID=ORCL)));User Id=system;Password=Strong@Passw0rd;",
                "IsEncryption": "N",
                "WorkingDirectoryPath": "../tmp/HDS/function/HDS_FN03",
                "Comment": "Oracle 기본 거래"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "FN04",
                "DataProvider": "MySQL",
                "ConnectionString": "Server=localhost;Port=3306;Uid=root;Pwd=Strong@Passw0rd;PersistSecurityInfo=True;SslMode=none;Charset=utf8;Allow User Variables=True;",
                "IsEncryption": "N",
                "WorkingDirectoryPath": "../tmp/HDS/function/HDS_FN04",
                "Comment": "MySQL 기본 거래"
            },
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "DataSourceID": "FN05",
                "DataProvider": "PostgreSQL",
                "ConnectionString": "Host=localhost;Port=5432;Database=postgres;User ID=postgres;Password=Strong@Passw0rd;",
                "IsEncryption": "N",
                "WorkingDirectoryPath": "../tmp/HDS/function/HDS_FN05",
                "Comment": "PostgreSQL 기본 거래"
            }
        ]
    }
}
QCN

repository 모듈 설정 확인하기

repository 모듈 참고하기

{
    "ModuleID": "repository",
    "Name": "repository",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "FileServerUrl": "http://localhost:8421",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "ContractBasePath": [
            "../contracts/repository"
        ],
        "ModuleLogFilePath": "../log/repository/module.log",
        "DatabaseContractPath": "../contracts/dbclient",
        "ModuleBasePath": "../modules/repository",
        "EventAction": [],
        "SubscribeAction": [ "repository.Events.RepositoryRequest" ],
        "XFrameOptions": "ALLOW-FROM http://127.0.0.1:8421,http://localhost:8421",
        "ContentSecurityPolicy": "frame-ancestors 'self' http://127.0.0.1:8421 http://localhost:8421;"
    }
}
QCN

transact 모듈 설정 확인하기

transact 모듈 참고하기

{
    "ModuleID": "transact",
    "Name": "transact",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "CircuitBreakResetSecond": 60,
        "IsValidationRequest": false,
        "IsAllowDynamicRequest": true,
        "AllowTenantTransactionCommands": [ "D" ],
        "IsLogServer": true,
        "LogServerUrl": "http://localhost:8421/logger/api/log/insert",
        "IsTransactionLogging": true,
        "IsTransactAggregate": true,
        "IsDataMasking": false,
        "MaskingChar": "*",
        "MaskingMethod": "Syn",
        "ContractBasePath": [
            "../contracts/transact"
        ],
        "AvailableEnvironment": [ "P", "D", "S" ],
        "IsCodeDataCache": true,
        "CodeDataCacheTimeout": 20,
        "ModuleBasePath": "../modules/transact",
        "TransactionLogBasePath": "../sqlite/aggregate",
        "UseApiAuthorize": false,
        "BypassAuthorizeIP": [
            "localhost",
            "127.0.0.1"
        ],
        "AllowRequestTransactions": {
            "HDS": [ "*" ]
        },
        "RoutingCommandUri": {
            "HDS|*|D|D": "http://localhost:8421/dbclient/api/query",
            "HDS|*|F|D": "http://localhost:8421/function/api/execution",
            "HDS|*|D|P": "http://localhost:8421/dbclient/api/query",
            "HDS|*|F|P": "http://localhost:8421/function/api/execution",
            "HDS|*|D|T": "http://localhost:8421/dbclient/api/query",
            "HDS|*|F|T": "http://localhost:8421/function/api/execution"
        },
        "EventAction": [],
        "SubscribeAction": [
            "transact.Events.TransactRequest"
        ],
        "PublicTransactions": [
            {
                "ApplicationID": "HDS",
                "ProjectID": "*",
                "TransactionID": "*"
            }
        ]
    }
}
QCN

logger 모듈 설정 확인하기

logger 모듈 참고하기

{
    "ModuleID": "logger",
    "Name": "logger",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "IsSQLiteCreateOnNotSettingRequest": true,
        "LogDeleteRepeatSecond": 43200,
        "ModuleBasePath": "../modules/logger",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "EventAction": [],
        "SubscribeAction": [],
        "DataSource": [
            {
                "ApplicationID": "HDS",
                "TableName": "TransactLog",
                "DataProvider": "SQLite",
                "RemovePeriod": -30,
                "ConnectionString": "URI=file:../sqlite/HDS/logger/transact.db;Journal Mode=Off;BinaryGUID=False;DateTimeFormat=Ticks;Version=3;",
                "IsEncryption": "N"
            }
        ]
    }
}
QCN

wwwroot 모듈 설정 확인하기

wwwroot 모듈 참고하기

libman.json 으로 화면 개발에 필요한 오픈소스 관리합니다.

{
    "ModuleID": "wwwroot",
    "Name": "wwwroot",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "ModuleLogFilePath": "../log/wwwroot/module.log",
        "ContractRequestPath": "view",
        "ContractBasePath": "../contracts/wwwroot",
        "WWWRootBasePath": "../modules/wwwroot/wwwroot"
    }
}
QCN

openapi 모듈 설정 확인하기

openapi 모듈 참고하기

{
    "ModuleID": "openapi",
    "Name": "openapi",
    "IsBundledWithHost": false,
    "Version": "1.0.0",
    "ModuleConfig": {
        "SystemID": "HANDSTACK",
        "ModuleBasePath": "../modules/openapi",
        "ManagerEmailID": "manager@handstack.kr",
        "ManagerSHA256Password": "48c691ca3e9d0e01bdaab5923534a1ebc01dcb52f87bddccbae6e185f3f481d9",
        "ModuleConfigurationUrl": "http://localhost:8421/openapi/api/managed/initialize-settings",
        "BusinessServerUrl": "http://localhost:8421/transact/api/transaction/execute",
        "ModuleLogFilePath": "../log/openapi/module.log",
        "IsLogServer": true,
        "LogServerUrl": "http://localhost:8421/logger/api/log/insert",
        "DataSource": {
            "ApplicationID": "HDS",
            "ProjectID": "HOA",
            "DataSourceID": "OPENAPIDB",
            "DataProvider": "SQLite",
            "ConnectionString": "URI=file:../sqlite/HDS/openapi/managed.db;Journal Mode=Off;BinaryGUID=False;DateTimeFormat=Ticks;Version=3;",
            "IsEncryption": "N"
        },
        "EventAction": [
            "dbclient.Events.ManagedRequest"
        ],
        "SubscribeAction": []
    }
}
QCN

요약 정리 및 Q&A

  • HandStack은 여러분이 클라이언트와 서버에서 업무 화면과 기능을 개발 할 때 필요한 기본적인 틀과 오픈소스를 제공합니다.
  • 덕분에 개발자는 복잡한 설정이나 기반 구조에 쏟는 시간을 줄이고, 핵심 비즈니스 로직 개발에만 집중할 수 있죠!
QCN